/*
 * OREKIT-X
 * Copyright 2002-2008 CS Communication & Systemes
 * 
 * Parts of this software package have been licensed to CS
 * Communication & Systemes (CS) under one or more contributor license
 * agreements.  See the NOTICE file distributed with this work for
 * additional information.
 *  
 * This is an experimental copy of OREKIT from www.orekit.org.
 * Please use the original OREKIT from orekit.org for normal work
 * unrelated to this research project.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you
 * may not use this file except in compliance with the License.  You
 * may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
 * implied.  See the License for the specific language governing
 * permissions and limitations under the License.
 */
package ore.bodies;

import ore.PositionVelocityChebyshev;
import static ore.bodies.JPLEphemerides.EphemerisType.* ;
import ore.errors.OrekitException;
import ore.time.ChronologicalComparator;

/** 
 * Constants defined in the JPL ephemerides binary files (DE 405, DE 406).
 * 
 * @author Luc Maisonobe
 * @author John Pritchard
 */
public abstract class JPLConstants
    extends java.util.TreeSet<PositionVelocityChebyshev>
{
    protected static JPLConstants Instance;

    public final static JPLConstants InstanceJPLConstants(){
        if (null != JPLConstants.Instance)
            return JPLConstants.Instance;
        else
            throw OrekitException.createIllegalStateException("data not bound");
    }
    public final static boolean HasJPLConstants(){
        return (null != JPLConstants.Instance);
    }
    public final static boolean HasNotJPLConstants(){
        return (null == JPLConstants.Instance);
    }

    /** Constant identifier.
     */
    public enum Constant {
        AU, EMRAT, GM1, GM2, GMB, GM4, GM5, GM6, GM7, GM8, GM9, GMS;
    }

    /** Get astronomical unit.
     * @return astronomical unit in meters
     * @exception OrekitException if constant not found
     */
    public static double GetAstronomicalUnit() throws OrekitException {
        if (null != JPLConstants.Instance)
            return 1000.0 * JPLConstants.Instance.au;
        else
            throw OrekitException.createIllegalStateException("data not bound");
    }
    /** Get Earth/Moon mass ratio.
     * @return Earth/Moon mass ratio
     * @exception OrekitException if constant not found
     */
    public static double GetEarthMoonMassRatio() throws OrekitException {
        if (null != JPLConstants.Instance)
            return JPLConstants.Instance.emrat;
        else
            throw OrekitException.createIllegalStateException("data not bound");
    }
    /** Get the gravitational coefficient of a body.
     * @param body body for which the gravitational coefficient is requested
     * @return gravitational coefficient in m<sup>3</sup>/s<sup>2</sup>
     * @exception OrekitException if constant not found
     */
    public static double GetGravitationalCoefficient(final JPLEphemerides.EphemerisType body)
        throws OrekitException
    {
        if (null != JPLConstants.Instance){
            final double rawGM;
            switch (body) {
            case MERCURY :
                rawGM = JPLConstants.Instance.gm1;
                break;
            case VENUS :
                rawGM = JPLConstants.Instance.gm2;
                break;
            case EARTH_MOON :
                rawGM = JPLConstants.Instance.gmb;
                break;
            case MARS :
                rawGM = JPLConstants.Instance.gm4;
                break;
            case JUPITER :
                rawGM = JPLConstants.Instance.gm5;
                break;
            case SATURN :
                rawGM = JPLConstants.Instance.gm6;
                break;
            case URANUS :
                rawGM = JPLConstants.Instance.gm7;
                break;
            case NEPTUNE :
                rawGM = JPLConstants.Instance.gm8;
                break;
            case PLUTO :
                rawGM = JPLConstants.Instance.gm9;
                break;
            case MOON :
                return (JPLConstants.Instance.gmb / (1.0 + JPLConstants.Instance.emrat));
            case SUN :
                rawGM = JPLConstants.Instance.gms;
                break;
            default :
                throw OrekitException.createInternalError(null);
            }

            final double au    = JPLConstants.GetAstronomicalUnit();
            return rawGM * au * au * au / (86400.0 * 86400.0);
        }
        else
            throw OrekitException.createIllegalStateException("data not bound");
    }
    /** Get a constant defined in the ephemerides headers.
     * <p>Note that since constants are defined in the JPL headers
     * files, they are available as soon as one file is available, even
     * if it doesn't match the desired central date. This is because the
     * header must be parsed before the dates can be checked.</p>
     * @param name name of the constant
     * @return value of the constant of NaN if the constant is not defined
     * @exception OrekitException if constants cannot be loaded
     */
    public static double GetConstant(final String name) throws OrekitException {
        if (null != JPLConstants.Instance)
            return JPLConstants.Instance.get(name);
        else
            throw OrekitException.createIllegalStateException("data not bound");
    }
    public static double GetConstant(final Constant constant) throws OrekitException {
        if (null != JPLConstants.Instance)
            return JPLConstants.Instance.get(constant);
        else
            throw OrekitException.createIllegalStateException("data not bound");
    }


    protected double au = Double.NaN, emrat = Double.NaN, gm1 = Double.NaN, gm2 = Double.NaN;
    protected double gmb = Double.NaN, gm4 = Double.NaN, gm5 = Double.NaN, gm6 = Double.NaN;
    protected double gm7 = Double.NaN, gm8 = Double.NaN, gm9 = Double.NaN, gms = Double.NaN;

    private volatile int setDefined;


    protected JPLConstants(){
        super(new ChronologicalComparator());
    }
    protected JPLConstants(JPLEphemerides set){
        super(new ChronologicalComparator());
        if (null != set)
            this.addAll(set);
    }


    public final double getAu(){
        return this.au;
    }
    public final double getEmrat(){
        return this.emrat;
    }
    public final double getGm1(){
        return this.gm1;
    }
    public final double getGm2(){
        return this.gm2;
    }
    public final double getGmb(){
        return this.gmb;
    }
    public final double getGm4(){
        return this.gm4;
    }
    public final double getGm5(){
        return this.gm5;
    }
    public final double getGm6(){
        return this.gm6;
    }
    public final double getGm7(){
        return this.gm7;
    }
    public final double getGm8(){
        return this.gm8;
    }
    public final double getGm9(){
        return this.gm9;
    }
    public final double getGms(){
        return this.gms;
    }
    public final boolean isSetDefined(){
        return (12 <= this.setDefined);
    }
    public final double get(final String name) {
        return this.get(Constant.valueOf(name));
    }
    public final double get(final Constant constant) {
        switch (constant){
        case AU:
            return this.au;
        case EMRAT:
            return this.emrat;
        case GM1:
            return this.gm1;
        case GM2:
            return this.gm2;
        case GMB:
            return this.gmb;
        case GM4:
            return this.gm4;
        case GM5:
            return this.gm5;
        case GM6:
            return this.gm6;
        case GM7:
            return this.gm7;
        case GM8:
            return this.gm8;
        case GM9:
            return this.gm9;
        case GMS:
            return this.gms;
        default:
            throw OrekitException.createIllegalStateException("unimplemented constant {0}",constant);
        }
    }
    public final void set(final String name, final double value) {
        this.set(Constant.valueOf(name),value);
    }
    public final void set(final Constant constant, final double value) {
        switch (constant){
        case AU:
            this.setDefined += 1;
            this.au = value;
            return;
        case EMRAT:
            this.setDefined += 1;
            this.emrat = value;
            return;
        case GM1:
            this.setDefined += 1;
            this.gm1 = value;
            return;
        case GM2:
            this.setDefined += 1;
            this.gm2 = value;
            return;
        case GMB:
            this.setDefined += 1;
            this.gmb = value;
            return;
        case GM4:
            this.setDefined += 1;
            this.gm4 = value;
            return;
        case GM5:
            this.setDefined += 1;
            this.gm5 = value;
            return;
        case GM6:
            this.setDefined += 1;
            this.gm6 = value;
            return;
        case GM7:
            this.setDefined += 1;
            this.gm7 = value;
            return;
        case GM8:
            this.setDefined += 1;
            this.gm8 = value;
            return;
        case GM9:
            this.setDefined += 1;
            this.gm9 = value;
            return;
        case GMS:
            this.setDefined += 1;
            this.gms = value;
            return;
        default:
            throw OrekitException.createIllegalStateException("unimplemented constant {0}",constant);
        }
    }
}
